package nagel_schreckenberg;

import java.util.ArrayList;
import java.util.Random;

import nagel_schreckenberg.CarFactory.Placement;
import nagel_schreckenberg.Lane.Direction;
import nagel_schreckenberg.Lane.Type;

public class Environment {
	
	private Random rnd;
	private ArrayList<Lane> ls;
	private ArrayList<Intersection> is;
	private int throughPutCounter;
	private int waitingTime;
	private int carCounter;
	
	public Environment(){
		rnd = new Random();
		ls = new ArrayList<Lane>();
		is = new ArrayList<Intersection>();
		throughPutCounter = 0;
		waitingTime = 0;
		carCounter = 0;
	}

	public void createSingleIntersection() throws Exception{
		FourIntersection fi = new FourIntersection();
		CarFactory f1 = new CarFactory();
		CarFactory f2 = new CarFactory();
		CarFactory f3 = new CarFactory();
		CarFactory f4 = new CarFactory();
		f1.setPlacement(Placement.NORTH);
		f2.setPlacement(Placement.EAST);
		f3.setPlacement(Placement.SOUTH);
		f4.setPlacement(Placement.WEST);
		CarDestructor d1 = new CarDestructor();
		CarDestructor d2 = new CarDestructor();
		CarDestructor d3 = new CarDestructor();
		CarDestructor d4 = new CarDestructor();
		Lane in1 = new Lane(100, 5);
		Lane in2 = new Lane(100, 5);
		Lane in3 = new Lane(100, 5);
		Lane in4 = new Lane(100, 5);
		in1.setType(Type.IN);
		in2.setType(Type.IN);
		in3.setType(Type.IN);
		in4.setType(Type.IN);
		Lane out1 = new Lane(100, 5);
		Lane out2 = new Lane(100, 5);
		Lane out3 = new Lane(100, 5);
		Lane out4 = new Lane(100, 5);
		out1.setType(Type.OUT);
		out2.setType(Type.OUT);
		out3.setType(Type.OUT);
		out4.setType(Type.OUT);
		fi.addLanes(in1, out1, in2, out2, in3, out3, in4, out4);
		in1.setDirection(Direction.NORTH);
		in2.setDirection(Direction.EAST);
		in3.setDirection(Direction.SOUTH);
		in4.setDirection(Direction.WEST);
		f1.addLanes(in1);
		f2.addLanes(in2);
		f3.addLanes(in3);
		f4.addLanes(in4);
		d1.addLanes(out1);
		d2.addLanes(out2);
		d3.addLanes(out3);
		d4.addLanes(out4);
		is.add(fi);
		is.add(f1);
		is.add(f2);
		is.add(f3);
		is.add(f4);
		is.add(d1);
		is.add(d2);
		is.add(d3);
		is.add(d4);
		ls.add(in1);
		ls.add(in2);
		ls.add(in3);
		ls.add(in4);
		ls.add(out1);
		ls.add(out2);
		ls.add(out3);
		ls.add(out4);
	}

	public void createRoundabout(){
		//TODO maybe later?
	}
	
	public void createMultiIntersection() throws Exception{
		CarFactory f1 = new CarFactory();
		f1.setPlacement(Placement.NORTH);
		is.add(f1);
		CarFactory f2 = new CarFactory();
		f2.setPlacement(Placement.NORTH);
		is.add(f2);
		CarFactory f3 = new CarFactory();
		f3.setPlacement(Placement.NORTH);
		is.add(f3);
		CarFactory f4 = new CarFactory();
		f4.setPlacement(Placement.EAST);
		is.add(f4);
		CarFactory f5 = new CarFactory();
		f5.setPlacement(Placement.EAST);
		is.add(f5);
		CarFactory f6 = new CarFactory();
		f6.setPlacement(Placement.EAST);
		is.add(f6);
		CarFactory f7 = new CarFactory();
		f7.setPlacement(Placement.SOUTH);
		is.add(f7);
		CarFactory f8 = new CarFactory();
		f8.setPlacement(Placement.SOUTH);
		is.add(f8);
		CarFactory f9 = new CarFactory();
		f9.setPlacement(Placement.SOUTH);
		is.add(f9);
		CarFactory f10 = new CarFactory();
		f10.setPlacement(Placement.WEST);
		is.add(f10);
		CarFactory f11 = new CarFactory();
		f11.setPlacement(Placement.WEST);
		is.add(f11);
		CarFactory f12 = new CarFactory();
		f12.setPlacement(Placement.WEST);
		is.add(f12);
		CarDestructor d1 = new CarDestructor();
		is.add(d1);
		CarDestructor d2 = new CarDestructor();
		is.add(d2);
		CarDestructor d3 = new CarDestructor();
		is.add(d3);
		CarDestructor d4 = new CarDestructor();
		is.add(d4);
		CarDestructor d5 = new CarDestructor();
		is.add(d5);
		CarDestructor d6 = new CarDestructor();
		is.add(d6);
		CarDestructor d7 = new CarDestructor();
		is.add(d7);
		CarDestructor d8 = new CarDestructor();
		is.add(d8);
		CarDestructor d9 = new CarDestructor();
		is.add(d9);
		CarDestructor d10 = new CarDestructor();
		is.add(d10);
		CarDestructor d11 = new CarDestructor();
		is.add(d11);
		CarDestructor d12 = new CarDestructor();
		is.add(d12);
		FourIntersection im = new FourIntersection("IM");
		is.add(im);
		FourIntersection i1 = new FourIntersection("1");
		is.add(i1);
		FourIntersection i2 = new FourIntersection("2");
		is.add(i2);
		FourIntersection i3 = new FourIntersection("3");
		is.add(i3);
		FourIntersection i4 = new FourIntersection("4");
		is.add(i4);
		FourIntersection i5 = new FourIntersection("5");
		is.add(i5);
		FourIntersection i6 = new FourIntersection("6");
		is.add(i6);
		FourIntersection i7 = new FourIntersection("7");
		is.add(i7);
		FourIntersection i8 = new FourIntersection("8");
		is.add(i8);
		
		Lane f1i1 = new Lane(100, 5); f1i1.setType(Type.IN); f1i1.setDirection(Direction.NORTH); ls.add(f1i1); f1.addLanes(f1i1);
		Lane i1d1 = new Lane(100, 5); i1d1.setType(Type.OUT); ls.add(i1d1); d1.addLanes(i1d1);
		Lane i2i1 = new Lane(100, 5); i2i1.setType(Type.IN); i2i1.setDirection(Direction.EAST); ls.add(i2i1);
		Lane i1i2 = new Lane(100, 5); i1i2.setType(Type.IN); i1i2.setDirection(Direction.WEST); ls.add(i1i2);
		Lane i4i1 = new Lane(100, 5); i4i1.setType(Type.IN); i4i1.setDirection(Direction.SOUTH); ls.add(i4i1);
		Lane i1i4 = new Lane(100, 5); i1i4.setType(Type.IN); i1i4.setDirection(Direction.NORTH); ls.add(i1i4);
		Lane f12i1 = new Lane(100, 5); f12i1.setType(Type.IN); f12i1.setDirection(Direction.WEST); ls.add(f12i1); f12.addLanes(f12i1);
		Lane i1d12 = new Lane(100, 5); i1d12.setType(Type.OUT); ls.add(i1d12); d12.addLanes(i1d12);
		i1.addLanes(f1i1, i1d1, i2i1, i1i2, i4i1, i1i4, f12i1, i1d12);
		
		Lane f2i2 = new Lane(100, 5); f2i2.setType(Type.IN); f2i2.setDirection(Direction.NORTH); ls.add(f2i2); f2.addLanes(f2i2);
		Lane i2d2 = new Lane(100, 5); i2d2.setType(Type.OUT); ls.add(i2d2); d2.addLanes(i2d2);
		Lane i3i2 = new Lane(100, 5); i3i2.setType(Type.IN); i3i2.setDirection(Direction.EAST); ls.add(i3i2);
		Lane i2i3 = new Lane(100, 5); i2i3.setType(Type.IN); i2i3.setDirection(Direction.WEST); ls.add(i2i3);
		Lane imi2 = new Lane(100, 5); imi2.setType(Type.IN); imi2.setDirection(Direction.SOUTH); ls.add(imi2);
		Lane i2im = new Lane(100, 5); i2im.setType(Type.IN); i2im.setDirection(Direction.NORTH); ls.add(i2im);
		i2.addLanes(f2i2, i2d2, i3i2, i2i3, imi2, i2im, i1i2, i2i1);
		
		Lane f3i3 = new Lane(100, 5); f3i3.setType(Type.IN); f3i3.setDirection(Direction.NORTH); ls.add(f3i3); f3.addLanes(f3i3);
		Lane i3d3 = new Lane(100, 5); i3d3.setType(Type.OUT); ls.add(i3d3); d3.addLanes(i3d3);
		Lane f4i3 = new Lane(100, 5); f4i3.setType(Type.IN); f4i3.setDirection(Direction.EAST); ls.add(f4i3); f4.addLanes(f4i3);
		Lane i3d4 = new Lane(100, 5); i3d4.setType(Type.OUT); ls.add(i3d4); d4.addLanes(i3d4);
		Lane i5i3 = new Lane(100, 5); i5i3.setType(Type.IN); i5i3.setDirection(Direction.SOUTH); ls.add(i5i3);
		Lane i3i5 = new Lane(100, 5); i3i5.setType(Type.IN); i3i5.setDirection(Direction.NORTH); ls.add(i3i5);
		i3.addLanes(f3i3, i3d3, f4i3, i3d4, i5i3, i3i5, i2i3, i3i2);
		
		Lane imi4 = new Lane(100, 5); imi4.setType(Type.IN); imi4.setDirection(Direction.EAST); ls.add(imi4);
		Lane i4im = new Lane(100, 5); i4im.setType(Type.IN); i4im.setDirection(Direction.WEST); ls.add(i4im);
		Lane i6i4 = new Lane(100, 5); i6i4.setType(Type.IN); i6i4.setDirection(Direction.SOUTH); ls.add(i6i4);
		Lane i4i6 = new Lane(100, 5); i4i6.setType(Type.IN); i4i6.setDirection(Direction.NORTH); ls.add(i4i6);
		Lane f11i4 = new Lane(100, 5); f11i4.setType(Type.IN); f11i4.setDirection(Direction.WEST); ls.add(f11i4); f11.addLanes(f11i4);
		Lane i4d11 = new Lane(100, 5); i4d11.setType(Type.OUT); ls.add(i4d11); d11.addLanes(i4d11);
		i4.addLanes(i1i4, i4i1, imi4, i4im, i6i4, i4i6, f11i4, i4d11);
		
		Lane i5im = new Lane(100, 5); i5im.setType(Type.IN); i5im.setDirection(Direction.EAST); ls.add(i5im);
		Lane imi5 = new Lane(100, 5); imi5.setType(Type.IN); imi5.setDirection(Direction.WEST); ls.add(imi5);
		Lane i7im = new Lane(100, 5); i7im.setType(Type.IN); i7im.setDirection(Direction.SOUTH); ls.add(i7im);
		Lane imi7 = new Lane(100, 5); imi7.setType(Type.IN); imi7.setDirection(Direction.NORTH); ls.add(imi7);
		im.addLanes(i2im, imi2, i5im, imi5, i7im, imi7, i4im, imi4);
		
		Lane f5i5 = new Lane(100, 5); f5i5.setType(Type.IN); f5i5.setDirection(Direction.EAST); ls.add(f5i5); f5.addLanes(f5i5);
		Lane i5d5 = new Lane(100, 5); i5d5.setType(Type.OUT); ls.add(i5d5); d5.addLanes(i5d5);
		Lane i8i5 = new Lane(100, 5); i8i5.setType(Type.IN); i8i5.setDirection(Direction.SOUTH); ls.add(i8i5);
		Lane i5i8 = new Lane(100, 5); i5i8.setType(Type.IN); i5i8.setDirection(Direction.NORTH); ls.add(i5i8);
		i5.addLanes(i3i5, i5i3, f5i5, i5d5, i8i5, i5i8, imi5, i5im);
		
		Lane i7i6 = new Lane(100, 5); i7i6.setType(Type.IN); i7i6.setDirection(Direction.EAST); ls.add(i7i6);
		Lane i6i7 = new Lane(100, 5); i6i7.setType(Type.IN); i6i7.setDirection(Direction.WEST); ls.add(i6i7);
		Lane f9i6 = new Lane(100, 5); f9i6.setType(Type.IN); f9i6.setDirection(Direction.SOUTH); ls.add(f9i6); f9.addLanes(f9i6);
		Lane i6d9 = new Lane(100, 5); i6d9.setType(Type.OUT); ls.add(i6d9); d9.addLanes(i6d9);
		Lane f10i6 = new Lane(100, 5); f10i6.setType(Type.IN); f10i6.setDirection(Direction.WEST); ls.add(f10i6); f10.addLanes(f10i6);
		Lane i6d10 = new Lane(100, 5); i6d10.setType(Type.OUT); ls.add(i6d10); d10.addLanes(i6d10);
		i6.addLanes(i4i6, i6i4, i7i6, i6i7, f9i6, i6d9, f10i6, i6d10);
		
		Lane i8i7 = new Lane(100, 5); i8i7.setType(Type.IN); i8i7.setDirection(Direction.EAST); ls.add(i8i7);
		Lane i7i8 = new Lane(100, 5); i7i8.setType(Type.IN); i7i8.setDirection(Direction.WEST); ls.add(i7i8);
		Lane f8i7 = new Lane(100, 5); f8i7.setType(Type.IN); f8i7.setDirection(Direction.SOUTH); ls.add(f8i7); f8.addLanes(f8i7);
		Lane i7d8 = new Lane(100, 5); i7d8.setType(Type.OUT); ls.add(i7d8); d8.addLanes(i7d8);
		i7.addLanes(imi7, i7im, i8i7, i7i8, f8i7, i7d8, i6i7, i7i6);
		
		Lane f6i8 = new Lane(100, 5); f6i8.setType(Type.IN); f6i8.setDirection(Direction.EAST); ls.add(f6i8); f6.addLanes(f6i8);
		Lane i8d6 = new Lane(100, 5); i8d6.setType(Type.OUT); ls.add(i8d6); d6.addLanes(i8d6);
		Lane f7i8 = new Lane(100, 5); f7i8.setType(Type.IN); f7i8.setDirection(Direction.SOUTH); ls.add(f7i8); f7.addLanes(f7i8);
		Lane i8d7 = new Lane(100, 5); i8d7.setType(Type.OUT); ls.add(i8d7); d7.addLanes(i8d7);
		i8.addLanes(i5i8, i8i5, f6i8, i8d6, f7i8, i8d7, i7i8, i8i7);
	}
	
	public void accelerate(){
		for(Lane l : ls){
			for(int i = 0; i < l.getVehicles().size(); i++){
					if(l.getVehicles().get(i).getSpeed() < l.getVehicles().get(i).getMaxSpeed() 
							&& l.getVehicles().get(i).getSpeed() < l.getSpeedLimit()){
						l.getVehicles().get(i).accelerate(1);
					}
					if(l.getVehicles().get(i).getSpeed() > l.getSpeedLimit()){
						l.getVehicles().get(i).setSpeed(l.getSpeedLimit());
					}
			}
		}
	}
	
	public void avoidCollision(){
		for(Lane l : ls){
			for(int i = 0; i < l.getVehicles().size(); i++){
					int d = l.distToNextVehicle(i)-1;
					if(d < l.getVehicles().get(i).getSpeed()){
						l.getVehicles().get(i).setSpeed(d);
					}
					if(!l.getPhase() && i == 0){
						d = Math.min(l.getSpeedLimit(), l.getVehicles().get(i).getMaxSpeed());
						d = Math.min(d, l.getLength()-l.getVehicles().get(i).getPosition());
						l.getVehicles().get(i).setSpeed(d);
					}
			}
		}		
	}
	
	public void randomSlowdown(){
		for(Lane l : ls){
			for(int i = 0; i < l.getVehicles().size(); i++){
					double r = rnd.nextDouble();
					if(r < Config.SLOWDOWN && l.getVehicles().get(i).getSpeed() > 0){
						l.getVehicles().get(i).setSpeed(l.getVehicles().get(i).getSpeed()-1);
					}
			}
		}
	}
	
	public void move(){
		for(Lane l : ls){
			for(int i = 0; i < l.getVehicles().size(); i++){
				Vehicle m = l.getVehicles().get(i);
				if(m.getMoved() == false){
				int newPos = m.getPosition() + m.getSpeed();
				m.rest();
				m.travel();
				if(i == 0){
					if(newPos > l.getLength()){
						newPos -= l.getLength();
						m.setPosition(newPos);
						l.getTarget().getOut(l.getDirection()).addVehicle(m);
						l.removeVehicle(i);
						if(l.getType() == Type.IN){
							throughPutCounter++;
						}
						if(l.getType() == Type.IN && l.getTarget().getOut(l.getDirection()).getType() == Type.OUT){
							waitingTime += m.getWaitingTime();
							carCounter++;
						}
					}
					else{
						m.setPosition(newPos);
					}
				}
				else{
					m.setPosition(newPos);
				}
				m.setMoved(true);
				}
			}
		}
		for(Lane l : ls){
			for(int i = 0; i < l.getVehicles().size(); i++){
				Vehicle m = l.getVehicles().get(i);
				m.setMoved(false);
			}
		}
	}

	public ArrayList<Lane> getLanes(){
		return ls;
	}
	
	public ArrayList<Intersection> getIntersections(){
		return is;
	}
	
	public int getThroughPut(){
		return throughPutCounter;
	}
	
	public double getWaitingTime(){
		return waitingTime/(double)carCounter;
	}
}
